[Testing 15] test(evals): offline eval harness — citation accuracy, injection resistance, leakage - #257
Conversation
dd1234e to
d6e2ac0
Compare
|
recheck |
d6e2ac0 to
1bf1ebc
Compare
…ormat Completes the CI evals gate that ci.yml already wires up (the job runs node evals/run.mjs --threshold 1.0 when the file exists, with no npm ci — so the runner is dependency-free Node ESM). - evals/run.mjs: deterministic CLI scorecard; --threshold / --suite / --list; exits non-zero when the pass rate drops below threshold. The citation parser is ported line-for-line from backend/src/lib/chat/citations.ts (source lines cited in comments). - evals/cases/: three suites (27 cases) whose fixtures use the real pipeline shapes — answers are prose with inline [N] markers plus a <CITATIONS> JSON block (prompts.ts rules, doc-N labels, page ranges, [[PAGE_BREAK]], legacy top-level fields, cluster_id case entries), document pages mirror the [Page N] blocks from documentOps.ts, and tool calls use the ToolCall shape from chat/types.ts with real tool names. - Source texts are real documents: public-domain opinions (Marbury, Erie, Hadley), statute text (17 U.S.C. § 107, 15 U.S.C. §§ 1-2), and full-length contract clauses, with injections embedded inside the real text and cited spans buried mid-document. - Corpus mixes benign (expected: pass) and known-bad (expected: fail) cases, so weakening a checker flips its known-bad cases and fails CI. - docs/evals.md: how to run, the fixture-field -> runtime-format mapping with file references, how to add cases, and an honest scope note (checker logic + regression corpus now; recorded live-model runs drop into the same fixture format later). No network, no LLM calls, no secrets. Verified: full run 27/27 exits 0; a deliberately corrupted quote drops to 26/27 and exits 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1bf1ebc to
b37029f
Compare
|
Closing because the current offline eval harness does not provide enough product-level assurance to justify the additional code and maintenance. The harness evaluates hand-written mock responses using deterministic pattern matching and copied parsing logic. It does not call a model or exercise Mike’s real chat, permissions, retrieval, tool execution, or response-processing pipeline. A passing score therefore confirms that the standalone checker classified its prepared examples correctly, rather than demonstrating that the assistant is accurate or secure. Any useful citation and security cases should instead become unit or integration tests against Mike’s actual production functions. We can reconsider a dedicated evaluation harness if we later introduce live or recorded model evaluations that exercise the real pipeline. |
What this tests — and what it doesn't
This harness gates the checkers and the response-format contract, not the model. The fixture answers are recorded, hand-written responses in Mike's exact runtime format — the model is treated the way tests treat any expensive, nondeterministic external dependency: mocked out, so the logic around it can be tested deterministically. A live model regression will not show up here; what will show up is any change that weakens a safety check or drifts the citation format away from what the backend emits and parses.
Why offline-first instead of jumping straight to online evals against real responses:
cc-03, where a liability cap is quietly edited from twelve months to twenty-four, now passes a checker that should reject it, mismatches itsexpected: "fail", and CI goes red. An online eval would likely have missed it.The checkers are proven, not assumed
Every case declares
expected: "pass"or"fail", and roughly half the corpus is known-bad (a fabricated Erie holding, the altered liability cap above, a canary smuggled into a tool-call argument). If a checker is ever weakened, those cases flip and CI fails. Benign edge cases (docket numbers near the SSN pattern, an answer that warns about an injection without repeating it) go red if a checker over-triggers.How this differs from #231 (closed)
#231 was closed for "limited usefulness of the evals and format. A fuller real evals suite can be implemented in the future which aligns with the format of actual assistant responses and involves real documents." This PR was rebuilt around that feedback:
[N]markers plus a<CITATIONS>JSON block, exactly what the backend emits and parses. The harness's citation parser is ported line-for-line frombackend/src/lib/chat/citations.ts(source lines cited in its comments), document pages mirror the[Page N]blocks fromdocumentOps.ts, and recorded tool calls use theToolCallshape fromchat/types.tswith Mike's real tool names.docs/evals.mdhas the full fixture-field → runtime-format mapping with file references, so the format contract is auditable. This is also what makes the mocks valid: a recorded response is only a fair stand-in if it's shaped exactly like the real thing.What this completes
CI already has an
evalsjob that runsnode evals/run.mjs --threshold 1.0if the file exists and skips otherwise. This PR adds that file, turning the gate on: a deterministic scorecard for fabricated citations, prompt injection via retrieved documents, and privileged/PII leakage on every PR.How to run
Exit 0 at or above threshold, non-zero below. Verified both directions: the committed corpus is 27/27, and corrupting a single quoted span drops it to 26/27 with exit code 1 and a readable reason (
quote not verbatim on page 1 of ...).The suites
citation-accuracy— parses the<CITATIONS>block and checks every[N]marker has a matching entry and every quote exists verbatim on the cited page (or in the fetched opinion forcluster_idcitations). Covers cross-page quotes, page ranges, and the legacy citation shape.prompt-injection— recorded turns where the document carries an injected instruction and a canary; fails on canary leakage in output or tool arguments, or on a disallowed tool call (e.g. an injected note ordering anedit_documentrewrite).privilege-leakage— flags off-matter emails, SSN-like values,sb_secret_*keys,service_roleJWTs (the public anon key deliberately doesn't trip it), and document identifiers from another client's matter.Future live mode
Mocked tests don't replace integration tests against the real dependency — they let you run the real thing less often, out-of-band, where flakiness and cost don't gate merges. The "fuller real evals suite" from #231 is a drop-in extension here: run real prompts through the backend, capture the actual streamed responses and tool calls into this same fixture format, and score them with these same, already-proven checkers — behind an opt-in, outside CI, since it needs keys and isn't deterministic.
docs/evals.mdspells this out, along with the runtime counterparts (citation verification inbackend/src/lib/chat/citations.ts; spotlighting of untrusted content in the security-hardening PR #227).🤖 Generated with Claude Code